package birdsVSZombiesServer;

import java.awt.event.KeyEvent;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Timer;
import java.util.TimerTask;


import org.jbox2d.common.Vec2;
import org.jbox2d.dynamics.joints.MouseJoint;
import org.jbox2d.dynamics.joints.MouseJointDef;

import birdsVSZombiesClient.GUI.BVZButton;
import birdsVSZombiesClient.GUI.BVZRectButton;
import birdsVSZombiesComponents.*;

import com.esotericsoftware.kryonet.Client;
import com.esotericsoftware.kryonet.Connection;
import com.esotericsoftware.kryonet.Listener;
import com.esotericsoftware.kryonet.Server;
import com.google.gson.Gson;

import fisica.FContact;
import fisica.FContactResult;
import fisica.FMouseJoint;
import fisica.FWorld;
import fisica.Fisica;


import processing.core.PApplet;
import processing.core.PConstants;
import processing.core.PFont;
import shared.BVZClientToServerMessages;
import shared.BVZConstants;
import shared.BVZServerToClientsEvents;
import shared.BVZServerToClientsMessages;
import shared.Network;

/**
 * The server class which runs the server and physical simulation.
 * @author Yang Liu
 * @version Version 0.0.4, 11/22/2010
 */
public class BirdsVSZombiesServerThread extends PApplet{
	
	//	For debug
	public boolean isOnlyServer = true;
	public boolean isUseSling = false;
	public int testnumber = 0;

	
	//	The kyro version of server
	public Server bvzServer;
	
	public int testTimer = 0;
	
	/*
	 * Variables for Server Status
	 */
	public int serverStat = INITCONNECTED;
	public static final int INITCONNECTED = 0;
	public static final int INITPHYS = 1;
	public static final int RUNPHYS = 2;
	public static final int GAMEOVER = 3;
	
	public Gameover gameoverDetection;
	
	/*
	 * Variables for Player Information
	 */
	public BVZPlayer[] bvzPlayerList = new BVZPlayer[2];
	public int bvzPlayerNum = 0;
	
	/*
	 * Variables for physical stuff
	 */
	
	public ArrayList<BaseBox> blocks;
	public ArrayList<BoundaryBox> boundaries;
	public ArrayList<Birds> birds;
	public ArrayList<Zombies> zombies;
	
	public MouseInteractionOrg[] mouseJoints = new MouseInteractionOrg[2];
	public MouseInteractionOrg[] mouseJointsOrg = new MouseInteractionOrg[2];
	
	public boolean[] isMouseJointsCreated = new boolean[2];
	
	//	Physical stuff from fisica
	public FWorld world;
	
	public FMouseJoint slingJoint;
	
	public MouseJoint slingJoint2;
	
	public boolean isSlingReleased = false;
	public int queueingBirdID = 0;
	public int attachBirdDelay = 2000;
	public Timer schedule;
	public boolean tryPlayingSchedule = false;
	
	/*
	 * Variables for Json Messages
	 */
	public Gson gson;
	public BVZClientToServerMessages incomingMessage;
	public BVZServerToClientsMessages outcomingMessage;
	
	/*
	 * Server GUI
	 */
	public BVZRectButton startButton;
	public PFont f;
	
	
	public void setup(){
		size(BVZConstants.GAMEWINDOWWIDTH, BVZConstants.GAMEWINDOWHEIGHT);
		frameRate(BVZConstants.GAMEFRAMERATE);
		
		//	Set up the server
		bvzServer = new Server();
		bvzServer.start();
		try {
			bvzServer.bind(Network.TCPport, Network.UDPport);
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		Network.register(bvzServer);
		
		bvzServer.addListener(new Listener(){
			public void received(Connection c, Object obj){
				if(obj instanceof BVZClientToServerMessages){
					incomingMessage = (BVZClientToServerMessages)obj;
					switch (incomingMessage.clientStat) {
						case BVZConstants.CLIENTCONNECTED:
							BVZServerToClientsMessages msg = new BVZServerToClientsMessages(BVZConstants.SERVERWELCOMEMESSAGE);
							c.sendTCP(msg);
							break;
						case BVZConstants.CLIENTJOININ:
							makeBVZPlayer(incomingMessage.playerRole);
							if(bvzPlayerNum == 2)
								callRunASimplePhys();
							break;
						case BVZConstants.CLIENTRUNNINGPHYS:
							switch (incomingMessage.mouseStat) {
								case BVZConstants.MOUSEPRESSED:
									createMouseJoint();
									break;
								case BVZConstants.MOUSEDRAGGED:
									updateMouseJoint();
									break;
								case BVZConstants.MOUSERELEASED:
									destoryMouseJoint();
									break;
								default:
									break;
							}
							
							break;
						default:
							break;
					}

				}
			}
		});
		
		f = createFont("Arial", 20, true);
		
		//	Init the fisica physical world
		Fisica.init(this);
		world = new FWorld();
		world.setGravity(0, 1000);
//		world.setEdges();

		
		AbstractBox2DShape.parent = this;
		AbstractBox2DShape.world = this.world;
		MouseInteraction.world = this.world;
		MouseInteractionOrg.world = this.world;
		BVZButton.parent = this;
		BVZServerToClientsMessages.parent = this;
		BVZServerToClientsEvents.parent = this;
		
		gson = new Gson();
		
//		startButton = new BVZRectButton("Click to start!");
	}
	
	public void draw(){
		background(255);
		smooth();
		
		serverSelfUpdate();
		
//		startButton.display();
//		startButton.update();
	}
	
	
	/**
	 * Update server according to different server status.
	 */
	public void serverSelfUpdate(){
		switch (serverStat) {
			case INITCONNECTED:
				break;
			case INITPHYS:
				initPhysWorld();
				break;
			case RUNPHYS:
				runPhys();
				break;
			case GAMEOVER:
				stopPhys();
				break;
			default:
				break;
		}
	}
	
	
	/**
	 * Change the server status to start the physical simulation.
	 */
	public void callRunASimplePhys(){
		serverStat = INITPHYS;
		
	}
	
	/**
	 * Initialize the physical world.
	 */
	public void initPhysWorld(){
		
		boundaries = new ArrayList<BoundaryBox>();
		blocks = new ArrayList<BaseBox>();
		birds = new ArrayList<Birds>();
		
		zombies = new ArrayList<Zombies>();
		
		gameoverDetection = new Gameover();
		
		//	Initial the environment 
		//	Set the edges
		BoundaryBox bottomBoundary = new BoundaryBox(600, 550, 1200, 100, BVZConstants.NOROLE);
		BoundaryBox leftBoundary = new BoundaryBox(0, 300, 10, 600, BVZConstants.NOROLE);
		BoundaryBox rightBoundary = new BoundaryBox(1200, 300, 10, 600, BVZConstants.NOROLE);
		BoundaryBox topBoundary = new BoundaryBox(600, 0, 1200, 10, BVZConstants.NOROLE);
		boundaries.add(bottomBoundary);
		boundaries.add(leftBoundary);
		boundaries.add(rightBoundary);
		boundaries.add(topBoundary);
		
		
		
		//	Initial the birds shooter
		NormalBird nb = new NormalBird( 200, 350);
		BlackBird bb = new BlackBird( 200, 400);
		WhiteBird wb = new WhiteBird( 150, 400);
		ToughBird tb = new ToughBird( 130, 400);
		birds.add(nb);
		birds.add(bb);
		birds.add(wb);
		birds.add(tb);
		
		// Initial the sling
		if(isUseSling){
//			slingJoint = new FMouseJoint(birds.get(queueingBirdID).baseCircle, birds.get(queueingBirdID).baseCircle.getX(), birds.get(queueingBirdID).baseCircle.getY());
//			
//			slingJoint.setDamping(0.3f);
//			slingJoint.setFrequency(100);
//			slingJoint.setAnchor(0, 0);
//			slingJoint.setMaxForce(1000.0f);
//			
//			world.add(slingJoint);
			
			MouseJointDef md = new MouseJointDef();
			md.body1 = world.getGroundBody();
			md.body2 = birds.get(queueingBirdID).baseCircle.getBox2DBody();
			
			Vec2 mp = Fisica.screenToWorld(birds.get(queueingBirdID).baseCircle.getX(), birds.get(queueingBirdID).baseCircle.getY());
			md.target.set(mp);
			
			md.maxForce = 1000.0f * birds.get(queueingBirdID).baseCircle.getBox2DBody().m_mass;
			md.frequencyHz = 20.0f;
			md.dampingRatio = 0.9f;
			
			birds.get(queueingBirdID).baseCircle.getBox2DBody().wakeUp();
			
			slingJoint2 = (MouseJoint) world.createJoint(md);

			
			schedule = new Timer();
		}
			
		//	Initial the zombies defender
		NormalZombie nz1 = new NormalZombie( 900.0f, 300.0f);
		PunkZombie nz2 = new PunkZombie( 950.0f, 300.0f);
		JacksonZombie nz3 = new JacksonZombie( 1000.0f, 300.0f);
		zombies.add(nz1);
		zombies.add(nz2);
		zombies.add(nz3);
		
		
		WoodBlockSmall wbs1 = new WoodBlockSmall( 1000.0f, 300.0f);
		blocks.add(wbs1);
		WoodBlockLarge wbs2 = new WoodBlockLarge( 800.0f, 360.0f);
		blocks.add(wbs2);
		StoneBlockFrame wbs5 = new StoneBlockFrame( 850.0f, 360.0f);
		blocks.add(wbs5);
		IceBlockMedium wbs6 = new IceBlockMedium( 900.0f, 400.0f);
		blocks.add(wbs6);
		IceBlockFrame wbs7 = new IceBlockFrame( 900.0f, 120.0f);
		blocks.add(wbs7);

		
		BVZServerToClientsMessages msg = new BVZServerToClientsMessages(BVZConstants.SERVERPHYSINITCOMPLETED);
		String msgJson = gson.toJson(msg) + "*";
		bvzServer.sendToAllTCP(msg);
		
//		physicalSim.start();
		
		serverStat = RUNPHYS;
	}
	
	/**
	 * Run a simple physical simulation and send data to Clients.
	 */
	public void runPhys(){
//		box2d.step();
		
		world.step();
		world.draw();
		
		if(gameoverDetection.isGameover(birds, zombies)){
			BVZServerToClientsEvents gameoverEvent = new BVZServerToClientsEvents(BVZConstants.GAMEOVER, gameoverDetection.whoIsTheWinner(birds, zombies));
			bvzServer.sendToAllTCP(gameoverEvent);
			
			serverStat = GAMEOVER;
			return;
		}
		
		//	Draw the helper shapes on the server. Optional.
		for (int i = 0; i < boundaries.size(); i++) {
			BoundaryBox bb = boundaries.get(i);
			bb.display();
		}
		
		//		Display the PhysCircles
		for (int i = 0; i < birds.size(); i++) {
			Birds c = birds.get(i);
			c.display();
		}
		
		for (int i = 0; i < zombies.size(); i++) {
			Zombies z = zombies.get(i);
			z.display();
		}
		
		for (int i = 0; i < blocks.size(); i++) {
			BaseBox b = (BaseBox)blocks.get(i);
			b.display();
		}
		
		if(isUseSling){
			shootTheBirds();
			
			println(slingJoint2.getReactionForce().x);
			
			//	Display the slingjoint.
			Vec2 v1 = slingJoint2.getAnchor1();
			Vec2 v2 = slingJoint2.getAnchor2();
			
			v1 = Fisica.worldToScreen(v1);
			v2 = Fisica.worldToScreen(v2);
			
			stroke(0);
			strokeWeight(1);
			line(v1.x, v1.y, v2.x, v2.y);			
		}
		
		//		Make a snap shot of the scene
		this.testTimer++;
		BVZServerToClientsMessages msg = new BVZServerToClientsMessages(0, BVZConstants.SERVERRUNPHYS, boundaries, birds, zombies, blocks);
		bvzServer.sendToAllTCP(msg);
	}
	
	public void stopPhys(){
		
	}
	
	/**
	 * Add players when a new client connected.
	 * @param bvzClient The clients which the current player is in.
	 */
	public void makeBVZPlayer(int playerRole){
		BVZPlayer bvzPlayer = new BVZPlayer(playerRole);
		bvzPlayerList[playerRole] = bvzPlayer;
		
		bvzPlayerNum++;
	}
		
	/**
	 * Create a mouse joint only when there are shapes at the mouse position and the shape is belong to the player.
	 *	When using sling, only the bird on the sling can be controlled.
	 */
	public void createMouseJoint(){
		mouseJoints[incomingMessage.playerRole] = new MouseInteractionOrg();
		
		if(isUseSling){
			if(incomingMessage.playerRole == BVZConstants.BIRDSSHOOTER){
				if(mouseJoints[incomingMessage.playerRole].getBodyAtClientMouse(incomingMessage) == birds.get(queueingBirdID).baseCircle.getBox2DBody()
						&& mouseJoints[incomingMessage.playerRole].getBodyAtClientMouse(incomingMessage).bodyMaster == incomingMessage.playerRole){
					mouseJoints[incomingMessage.playerRole].bind(incomingMessage.mousePosX, incomingMessage.mousePosY, mouseJoints[incomingMessage.playerRole].getBodyAtClientMouse(incomingMessage));
					mouseJoints[incomingMessage.playerRole].update(incomingMessage.mousePosX, incomingMessage.mousePosY);
					isMouseJointsCreated[incomingMessage.playerRole] = true;
					println("BS Mouse Joint Created!!!!");
				}
			}
			else if(incomingMessage.playerRole == BVZConstants.ZOMBIESDEFENDER){
				if(mouseJoints[incomingMessage.playerRole].getBodyAtClientMouse(incomingMessage) != null && mouseJoints[incomingMessage.playerRole].getBodyAtClientMouse(incomingMessage).bodyMaster == incomingMessage.playerRole){
					mouseJoints[incomingMessage.playerRole].bind(incomingMessage.mousePosX, incomingMessage.mousePosY, mouseJoints[incomingMessage.playerRole].getBodyAtClientMouse(incomingMessage));
					mouseJoints[incomingMessage.playerRole].update(incomingMessage.mousePosX, incomingMessage.mousePosY);
					isMouseJointsCreated[incomingMessage.playerRole] = true;
					println("ZD Mouse Joint Created!!!!");
				}
			}
		}
		else{
			if(mouseJoints[incomingMessage.playerRole].getBodyAtClientMouse(incomingMessage) != null && mouseJoints[incomingMessage.playerRole].getBodyAtClientMouse(incomingMessage).bodyMaster == incomingMessage.playerRole){
				mouseJoints[incomingMessage.playerRole].bind(incomingMessage.mousePosX, incomingMessage.mousePosY, mouseJoints[incomingMessage.playerRole].getBodyAtClientMouse(incomingMessage));
				mouseJoints[incomingMessage.playerRole].update(incomingMessage.mousePosX, incomingMessage.mousePosY);
				isMouseJointsCreated[incomingMessage.playerRole] = true;
				println("Mouse Joint Created!!!!");
			}
		}
		
		
	}
	
	/**
	 * Update a mouse joint
	 */
	public void updateMouseJoint(){
		mouseJoints[incomingMessage.playerRole].update(incomingMessage.mousePosX, incomingMessage.mousePosY);
//		println("Mouse Joint updated!!!");
	}
	
	/**
	 * Destroy a mouse joint
	 */
	public void destoryMouseJoint(){
		if(isMouseJointsCreated[incomingMessage.playerRole]){
			mouseJoints[incomingMessage.playerRole].destory();
			
//			slingJoint.releaseGrabbedBody();
//			slingJoint = null;
			if(incomingMessage.playerRole == BVZConstants.BIRDSSHOOTER){
				isSlingReleased = true;
			}
			isMouseJointsCreated[incomingMessage.playerRole] = false;
			
			println("Mouse Joint Destoried!!!!");

		}
	}
	
	/**
	 * Shoot the bird when release the sling
	 */
	public void shootTheBirds(){
		if(isSlingReleased){
//			Vec2 birdPos = new Vec2(slingJoint.getGrabbedBody().getX(), slingJoint.getGrabbedBody().getY());
//			Vec2 slingTarget =  new Vec2(slingJoint.getTargetX(), slingJoint.getTargetY());
//			
//			float dst = birdPos.sub(slingTarget).length();
//			
//			//	Release the sling when the birds reach the shooting point
//			if(dst <= 30){
//				
//				queueingBirdID++;
//				if(queueingBirdID <= 3){
////					println("released!!!");
//					slingJoint.releaseGrabbedBody();
//					slingJoint.setGrabbedBodyAndTarget(boundaries.get(0).baseBox, 200, 350);
//					schedule.schedule(new AttachToSling(), attachBirdDelay);
//				}
//				else{
//					slingJoint.releaseGrabbedBody();
//					//	Set the mousejoint grabbed body to the ground to prevent crush.
//					//	TODO Find a better way to do this.
//					slingJoint.setGrabbedBodyAndTarget(boundaries.get(0).baseBox, 200, 350);
////					println("Shot all the birds!!!");
//				}
//				isSlingReleased = false;
//			}

			Vec2 birdPos = Fisica.worldToScreen(slingJoint2.getBody2().getPosition().x, slingJoint2.getBody2().getPosition().y);
			Vec2 slingTarget =  Fisica.worldToScreen(slingJoint2.m_target.x, slingJoint2.m_target.y);
			
			float dst = birdPos.sub(slingTarget).length();
			
			//	Release the sling when the birds reach the shooting point
			if(dst <= 30){
				
				queueingBirdID++;
				if(queueingBirdID <= 3){
//					println("released!!!");
					slingJoint2.m_body2 = null;
					slingJoint2.m_body2 = boundaries.get(0).baseBox.getBox2DBody();
					slingJoint2.setTarget(Fisica.screenToWorld(200, 350));
					schedule.schedule(new AttachToSling(), attachBirdDelay);
				}
				else{
					slingJoint2.m_body2 = null;
					//	Set the mousejoint grabbed body to the ground to prevent crush.
					//	TODO Find a better way to do this.
					slingJoint2.m_body2 = boundaries.get(0).baseBox.getBox2DBody();
					slingJoint2.setTarget(Fisica.screenToWorld(200, 350));
//					println("Shot all the birds!!!");
				}
				isSlingReleased = false;
			}

			
		}
		
//		println(slingJoint.getGrabbedBody().getX() + "," + slingJoint.getReactionForceX());
	}
	
	/*********************************************************************
	 * Attach the bird to the sling.
	 ********************************************************************/
	public class AttachToSling extends TimerTask{
//		public AttachToSling() {
//			println("New TimerTask started.");
////			birds.get(queueingBirdID).baseCircle.getBox2DBody().wakeUp();
//			birds.get(queueingBirdID).baseCircle.addForce(0, 100);
//			slingJoint.setGrabbedBodyAndTarget(birds.get(queueingBirdID).baseCircle, 200, 350);
//
//		}

		@Override
		public void run() {
//			world.add(slingJoint);
			birds.get(queueingBirdID).baseCircle.addForce(0, 100);
//			slingJoint.setGrabbedBodyAndTarget(birds.get(queueingBirdID).baseCircle, 200, 350);
			slingJoint2.m_body1 = world.getGroundBody();
			slingJoint2.m_body2 = birds.get(queueingBirdID).baseCircle.getBox2DBody();

			println("Attached");
//			birds.get(queueingBirdID).baseCircle.setPosition(200, 350);
//			birds.get(queueingBirdID).baseCircle.addForce(0, 100);
//			birds.get(queueingBirdID).baseCircle.getBox2DBody().wakeUp();
//			schedule.cancel();
			
		}
	}
	
	//	Attach the birds to the sling
	public void attachBirds(){
		
	}
	
	// For collision detection
	
	public void contactStarted(FContact contact){
//		BVZServerToClientsMessages msg = new BVZServerToClientsMessages(1, BVZConstants.SERVERRUNPHYS, boundaries, circles, boxes);
//		bvzServer.sendToAllTCP(msg);
	}
	
	 public void contactResult(FContactResult result) {
	   // Draw an ellipse where the contact took place and as big as the normal impulse of the contact
		 
		 fill(0, 255, 0);
		 //	Get the contact between birds and (zombies and bolcks)
		 if(result.getBody1().getBox2DBody().bodyMaster != result.getBody2().getBox2DBody().bodyMaster
				 && (result.getBody1().getBox2DBody().bodyMaster == BVZConstants.BIRDSSHOOTER || result.getBody1().getBox2DBody().bodyMaster == BVZConstants.ZOMBIESDEFENDER)
				 && (result.getBody2().getBox2DBody().bodyMaster == BVZConstants.BIRDSSHOOTER || result.getBody2().getBox2DBody().bodyMaster == BVZConstants.ZOMBIESDEFENDER))
		 {
			 
			 //	Get the contact which is strong enough
			 if(result.getNormalImpulse() > 3000){
				 ellipse(result.getX(), result.getY(), result.getNormalImpulse()/10, result.getNormalImpulse()/10);

				 BirdsVSZombiesCollision col = new BirdsVSZombiesCollision(result, this);
				 
				 col.updateColItems();
				 
				 //	Send the collision event to the clients
				 BVZServerToClientsEvents colEvent = new BVZServerToClientsEvents(BVZConstants.COLLISIONEVENT, col);
				 bvzServer.sendToAllTCP(colEvent);
			 }
		 }
	 }

//	
//	void contactPersisted(FContact contact) {
//	   // Draw in blue an ellipse where the contact took place
//	   fill(0, 0, 170);
//	   ellipse(contact.getX(), contact.getY(), 10, 10);
//	 }
	 
	 public void contactEnded(FContact contact){
//		BVZServerToClientsMessages msg = new BVZServerToClientsMessages(1, BVZConstants.SERVERRUNPHYS, boundaries, circles, boxes);
//		println("ddd");
//		bvzServer.sendToAllTCP(msg);
	 }
	
	 public void keyPressed(){
		switch (serverStat) {
			case INITCONNECTED:
				break;
			case INITPHYS:
				break;
			case RUNPHYS:
				 if(keyCode == KeyEvent.VK_SPACE){
//					 println("delete!!");
//					 world.remove(birds.get(0).baseCircle);
//					 birds.remove(0);
				 }
				break;
			default:
				break;
		}

	 }
	 
	public static void main(String _args[]) {
		PApplet.main(new String[] { birdsVSZombiesServer.BirdsVSZombiesServerThread.class.getName() });
	}
}






